//===-- cl_options.cpp ----------------------------------------------------===//
//
//                         LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//

#include "driver/cl_options.h"

#include "gen/logger.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Operator.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Target/TargetMachine.h"

namespace opts {

// This vector is filled by parseCommandLine in main.cpp.
llvm::SmallVector<const char *, 32> allArguments;

cl::OptionCategory linkingCategory("Linking options");

// Positional options first, in order:
cl::list<std::string> fileList(cl::Positional, cl::desc("files"));

cl::list<std::string> runargs(
    "run",
    cl::desc(
        "Runs the resulting program, passing the remaining arguments to it"),
    cl::Positional, cl::PositionalEatsArgs);

cl::opt<bool> invokedByLDMD("ldmd", cl::desc("Invoked by LDMD?"),
                            cl::ZeroOrMore, cl::ReallyHidden);

static cl::opt<Diagnostic, true> useDeprecated(
    cl::desc("Allow deprecated language features and symbols:"), cl::ZeroOrMore,
    cl::location(global.params.useDeprecated), cl::init(DIAGNOSTICinform),
    cl::values(
        clEnumValN(DIAGNOSTICoff, "d",
                   "Silently allow deprecated features and symbols"),
        clEnumValN(DIAGNOSTICinform, "dw",
                   "Issue a message when deprecated features or "
                   "symbols are used (default)"),
        clEnumValN(
            DIAGNOSTICerror, "de",
            "Issue an error when deprecated features or symbols are used "
            "(halt compilation)")));

cl::opt<bool> compileOnly("c", cl::desc("Compile only, do not link"),
                          cl::ZeroOrMore);

static cl::opt<bool, true> createStaticLib("lib", cl::ZeroOrMore,
                                           cl::desc("Create static library"),
                                           cl::location(global.params.lib));

static cl::opt<bool, true>
    createSharedLib("shared", cl::desc("Create shared library (DLL)"),
                    cl::ZeroOrMore, cl::location(global.params.dll));

cl::opt<SymbolVisibility> symbolVisibility(
    "fvisibility", cl::ZeroOrMore, cl::desc("Default visibility of symbols"),
    cl::init(SymbolVisibility::default_),
    cl::values(
        clEnumValN(
            SymbolVisibility::default_, "default",
            "Hidden for Windows targets without -shared, otherwise public"),
        clEnumValN(SymbolVisibility::hidden, "hidden",
                   "Only export symbols marked with 'export'"),
        clEnumValN(SymbolVisibility::public_, "public", "Export all symbols")));

cl::opt<DLLImport, true> dllimport(
    "dllimport", cl::ZeroOrMore, cl::location(global.params.dllimport),
    cl::desc("Windows only: which extern(D) global variables to dllimport "
             "implicitly if not defined in a root module"),
    cl::values(
        clEnumValN(DLLImport::none, "none",
                   "None (default with -link-defaultlib-shared=false)"),
        clEnumValN(DLLImport::defaultLibsOnly, "defaultLibsOnly",
                   "Only druntime/Phobos symbols (default with "
                   "-link-defaultlib-shared and -fvisibility=hidden)."),
        clEnumValN(DLLImport::all, "all",
                   "All (default with -link-defaultlib-shared and "
                   "-fvisibility=public)")));

static cl::opt<bool, true> verbose("v", cl::desc("Verbose"), cl::ZeroOrMore,
                                   cl::location(global.params.v.verbose));

static cl::opt<bool, true>
    vcolumns("vcolumns",
             cl::desc("Print character (column) numbers in diagnostics"),
             cl::ZeroOrMore, cl::location(global.params.v.showColumns));

static cl::opt<bool, true>
    vgc("vgc", cl::desc("List all gc allocations including hidden ones"),
        cl::ZeroOrMore, cl::location(global.params.v.gc));

// Dummy data type for custom parsers where the help output shouldn't display
// any value.
using DummyDataType = bool;

// `-vtemplates[=list-instances]` parser.
struct VTemplatesParser : public cl::parser<DummyDataType> {
  explicit VTemplatesParser(cl::Option &O) : cl::parser<DummyDataType>(O) {}

  bool parse(cl::Option &O, llvm::StringRef /*ArgName*/, llvm::StringRef Arg,
             DummyDataType & /*Val*/) {
    global.params.v.templates = true;

    if (Arg.empty()) {
      return false;
    }

    if (Arg == "list-instances") {
      global.params.v.templatesListInstances = true;
      return false;
    }

    return O.error("unsupported value '" + Arg + "'");
  }
};

static cl::opt<DummyDataType, false, VTemplatesParser> vtemplates(
    "vtemplates", cl::ZeroOrMore, cl::ValueOptional,
    cl::desc("List statistics on template instantiations\n"
             "Use -vtemplates=list-instances to additionally show all "
             "instantiation contexts for each template"));

static cl::opt<bool, true> verbose_cg("v-cg", cl::desc("Verbose codegen"),
                                      cl::ZeroOrMore,
                                      cl::location(global.params.verbose_cg));

static cl::opt<bool, true> verbose_cg_ast("vcg-ast", cl::ZeroOrMore, cl::Hidden,
                                          cl::desc("Write AST to .cg file"),
                                          cl::location(global.params.vcg_ast));

static cl::opt<unsigned, true> errorLimit(
    "verrors", cl::ZeroOrMore, cl::location(global.params.v.errorLimit),
    cl::desc("Limit the number of error messages (0 means unlimited)"));

static cl::opt<bool, true>
    showGaggedErrors("verrors-spec", cl::ZeroOrMore,
                     cl::location(global.params.v.showGaggedErrors),
                     cl::desc("Show errors from speculative compiles such as "
                              "__traits(compiles,...)"));

static cl::opt<bool, true> printErrorContext(
    "verrors-context", cl::ZeroOrMore,
    cl::location(global.params.v.printErrorContext),
    cl::desc(
        "Show error messages with the context of the erroring source line"));

static cl::opt<MessageStyle, true> verrorStyle(
    "verror-style", cl::ZeroOrMore, cl::location(global.params.v.messageStyle),
    cl::desc(
        "Set the style for file/line number annotations on compiler messages"),
    cl::values(
        clEnumValN(MessageStyle::digitalmars, "digitalmars",
                   "'file(line[,column]): message' (default)"),
        clEnumValN(MessageStyle::gnu, "gnu",
                   "'file:line[:column]: message', conforming to the GNU "
                   "standard used by gcc and clang")),
    cl::init(MessageStyle::digitalmars));

static cl::opt<unsigned, true>
    verrorSupplements("verror-supplements", cl::ZeroOrMore,
                      cl::location(global.params.v.errorSupplementLimit),
                      cl::desc("Limit the number of supplemental messages for "
                               "each error (0 means unlimited)"));

static cl::opt<Diagnostic, true> warnings(
    cl::desc("Warnings:"), cl::ZeroOrMore, cl::location(global.params.warnings),
    cl::values(
        clEnumValN(DIAGNOSTICerror, "w",
                   "Enable warnings as errors (compilation will halt)"),
        clEnumValN(DIAGNOSTICinform, "wi",
                   "Enable warnings as messages (compilation will continue)")),
    cl::init(DIAGNOSTICoff));

static cl::opt<bool, true> ignoreUnsupportedPragmas(
    "ignore", cl::desc("Ignore unsupported pragmas (default)"), cl::ZeroOrMore,
    cl::location(global.params.ignoreUnsupportedPragmas));

static cl::opt<CppStdRevision, true> cplusplus(
    "extern-std", cl::ZeroOrMore,
    cl::desc("C++ standard for name mangling compatibility"),
    cl::location(global.params.cplusplus),
    cl::values(
        clEnumValN(CppStdRevisionCpp98, "c++98",
                   "Sets `__traits(getTargetInfo, \"cppStd\")` to `199711`"),
        clEnumValN(
            CppStdRevisionCpp11, "c++11",
            "Sets `__traits(getTargetInfo, \"cppStd\")` to `201103` (default)"),
        clEnumValN(CppStdRevisionCpp14, "c++14",
                   "Sets `__traits(getTargetInfo, \"cppStd\")` to `201402`"),
        clEnumValN(CppStdRevisionCpp17, "c++17",
                   "Sets `__traits(getTargetInfo, \"cppStd\")` to `201703`"),
        clEnumValN(CppStdRevisionCpp20, "c++20",
                   "Sets `__traits(getTargetInfo, \"cppStd\")` to `202002`")));

static cl::opt<unsigned char, true> debugInfo(
    cl::desc("Generating debug information:"), cl::ZeroOrMore,
    cl::values(
        clEnumValN(1, "g", "Add symbolic debug info"),
        clEnumValN(2, "gc",
                   "Add symbolic debug info, optimize for non D debuggers"),
        clEnumValN(3, "gline-tables-only", "Add line tables only")),
    cl::location(global.params.symdebug), cl::init(0));

cl::opt<bool> emitDwarfDebugInfo(
    "gdwarf", cl::ZeroOrMore,
    cl::desc("Emit DWARF debuginfo (instead of CodeView) for MSVC targets"));

cl::opt<bool> noAsm("noasm", cl::desc("Disallow use of inline assembler"),
                    cl::ZeroOrMore);

// Output file options
cl::opt<bool> dontWriteObj("o-", cl::desc("Do not write object file"),
                           cl::ZeroOrMore);

cl::opt<std::string> objectFile("of", cl::value_desc("filename"), cl::Prefix,
                                cl::desc("Use <filename> as output file name"),
                                cl::ZeroOrMore);

cl::opt<std::string> objectDir("od", cl::value_desc("directory"), cl::Prefix,
                               cl::desc("Write object files to <directory>"),
                               cl::ZeroOrMore);

cl::opt<std::string>
    soname("soname", cl::value_desc("soname"), cl::Hidden, cl::Prefix,
           cl::desc("Use <soname> as output shared library soname"),
           cl::ZeroOrMore);

// Output format options
cl::opt<bool> output_bc("output-bc", cl::desc("Write LLVM bitcode"),
                        cl::ZeroOrMore);

cl::opt<bool> output_ll("output-ll", cl::desc("Write LLVM IR"), cl::ZeroOrMore);

cl::opt<bool> output_mlir("output-mlir", cl::desc("Write MLIR"),
    cl::ZeroOrMore);

cl::opt<bool> output_s("output-s", cl::desc("Write native assembly"),
                       cl::ZeroOrMore);

cl::opt<cl::boolOrDefault> output_o("output-o", cl::ZeroOrMore,
                                    cl::desc("Write native object"));

static cl::opt<bool, true>
    cleanupObjectFiles("cleanup-obj", cl::ZeroOrMore, cl::ReallyHidden,
                       cl::desc("Remove generated object files on success"),
                       cl::location(global.params.cleanupObjectFiles));

// DDoc options
static cl::opt<bool, true> doDdoc("D", cl::desc("Generate documentation"),
                                  cl::location(global.params.ddoc.doOutput),
                                  cl::ZeroOrMore);

cl::opt<std::string>
    ddocDir("Dd", cl::desc("Write documentation file to <directory>"),
            cl::value_desc("directory"), cl::Prefix, cl::ZeroOrMore);

cl::opt<std::string>
    ddocFile("Df", cl::desc("Write documentation file to <filename>"),
             cl::value_desc("filename"), cl::Prefix, cl::ZeroOrMore);

// Json options
static cl::opt<bool, true> doJson("X", cl::desc("Generate JSON file"),
                                  cl::ZeroOrMore,
                                  cl::location(global.params.json.doOutput));

cl::opt<std::string> jsonFile("Xf", cl::desc("Write JSON file to <filename>"),
                              cl::value_desc("filename"), cl::Prefix,
                              cl::ZeroOrMore);

// supported by DMD, but still undocumented
cl::list<std::string> jsonFields("Xi", cl::ReallyHidden, cl::value_desc("field"));

// Header generation options
static cl::opt<bool, true>
    doHdrGen("H", cl::desc("Generate 'header' file"), cl::ZeroOrMore,
             cl::location(global.params.dihdr.doOutput));

cl::opt<std::string> hdrDir("Hd", cl::ZeroOrMore, cl::Prefix,
                            cl::desc("Write 'header' file to <directory>"),
                            cl::value_desc("directory"));

cl::opt<std::string> hdrFile("Hf", cl::ZeroOrMore, cl::Prefix,
                             cl::desc("Write 'header' file to <filename>"),
                             cl::value_desc("filename"));

cl::opt<bool>
    hdrKeepAllBodies("Hkeep-all-bodies", cl::ZeroOrMore,
                     cl::desc("Keep all function bodies in .di files"));

// C++ header generation options

// `-HC[=silent|verbose]` parser. Required for defaulting to `silent`.
struct HCParser : public cl::parser<DummyDataType> {
  explicit HCParser(cl::Option &O) : cl::parser<DummyDataType>(O) {}

  bool parse(cl::Option &O, llvm::StringRef /*ArgName*/, llvm::StringRef Arg,
             DummyDataType & /*Val*/) {
    global.params.cxxhdr.doOutput = true;

    if (Arg.empty() || Arg == "silent") {
      return false;
    }

    if (Arg == "verbose") {
      global.params.cxxhdr.fullOutput = true;
      return false;
    }

    return O.error("unsupported value '" + Arg + "'");
  }
};

static cl::opt<DummyDataType, false, HCParser>
    doCxxHdrGen("HC", cl::ZeroOrMore, cl::ValueOptional,
                cl::desc("Write C++ 'header' equivalent to stdout\n"
                         "Use -HC=verbose to add comments for ignored "
                         "declarations (e.g. extern(D))"));

cl::opt<std::string>
    cxxHdrDir("HCd", cl::ZeroOrMore, cl::Prefix,
              cl::desc("Write C++ 'header' file to <directory>"),
              cl::value_desc("directory"));

cl::opt<std::string>
    cxxHdrFile("HCf", cl::ZeroOrMore, cl::Prefix,
               cl::desc("Write C++ 'header' file to <filename> instead of stdout"),
               cl::value_desc("filename"));

cl::opt<std::string> mixinFile("mixin", cl::ZeroOrMore,
                               cl::desc("Expand and save mixins to <filename>"),
                               cl::value_desc("filename"));

static cl::opt<bool, true> unittest("unittest", cl::ZeroOrMore,
                                    cl::desc("Compile in unit tests"),
                                    cl::location(global.params.useUnitTests));

cl::opt<std::string>
    cacheDir("cache",
             cl::desc("Enable compilation cache, using <cache dir> to "
                      "store cache files"),
             cl::value_desc("cache dir"), cl::ZeroOrMore);

static StringsAdapter strImpPathStore("J", global.params.fileImppath);
static cl::list<std::string, StringsAdapter> stringImportPaths(
    "J", cl::desc("Look for string imports also in <directory>"),
    cl::value_desc("directory"), cl::location(strImpPathStore), cl::Prefix);

static cl::opt<bool, true> addMain(
    "main", cl::ZeroOrMore, cl::location(global.params.addMain),
    cl::desc(
        "Add default main() if not present already (e.g. for unittesting)"));

// -d-debug is a bit messy, it has 3 modes:
// -d-debug=ident, -d-debug=level and -d-debug (without argument)
// The last one represents `-d-debug=1`, so it needs some special handling:
std::vector<std::string> debugArgs;

struct D_DebugStorage {
  void push_back(const std::string &str) {
    debugArgs.push_back(str.empty() ? "1" : str);
  }
};

static D_DebugStorage dds;

// -debug is already declared in LLVM (at least, in debug builds),
// so we need to be a bit more verbose.
static cl::list<std::string, D_DebugStorage> debugVersionsOption(
    "d-debug", cl::location(dds), cl::CommaSeparated, cl::ValueOptional,
    cl::desc("Compile in debug code >= <level> or identified by <idents>"),
    cl::value_desc("level/idents"));

// -version is also declared in LLVM, so again we need to be a bit more verbose.
cl::list<std::string> versions(
    "d-version", cl::CommaSeparated, cl::value_desc("level/idents"),
    cl::desc("Compile in version code >= <level> or identified by <idents>"));

cl::list<std::string> transitions(
    "transition", cl::CommaSeparated, cl::value_desc("name"),
    cl::desc("Help with language change identified by <name>, use ? for list"));

cl::list<std::string>
    previews("preview", cl::CommaSeparated, cl::value_desc("name"),
             cl::desc("Enable an upcoming language change "
                      "identified by <name>, use ? for list"));

cl::list<std::string> reverts(
    "revert", cl::CommaSeparated, cl::value_desc("name"),
    cl::desc("Revert language change identified by <name>, use ? for list"));

cl::list<std::string>
    linkerSwitches("L", cl::desc("Pass <linkerflag> to the linker"),
                   cl::value_desc("linkerflag"), cl::cat(linkingCategory),
                   cl::Prefix);

cl::list<std::string> ccSwitches(
    "Xcc", cl::value_desc("ccflag"), cl::cat(linkingCategory),
    cl::desc("Pass <ccflag> to GCC/Clang for linking/preprocessing"));

cl::list<std::string> cppSwitches("P", cl::value_desc("cppflag"), cl::Prefix,
                                  cl::desc("Pass <cppflag> to C preprocessor"));

cl::opt<std::string> moduleDeps(
    "deps", cl::ValueOptional, cl::ZeroOrMore, cl::value_desc("filename"),
    cl::desc("Write module dependencies to <filename> (only imports). "
             "'-deps' alone prints module dependencies "
             "(imports/file/version/debug/lib)"));

cl::opt<std::string>
    makeDeps("makedeps", cl::ValueOptional, cl::ZeroOrMore,
             cl::value_desc("filename"),
             cl::desc("Write module dependencies in Makefile compatible format "
                      "to <filename>/stdout (only imports)"));

cl::opt<bool> m32bits("m32", cl::desc("32 bit target"), cl::ZeroOrMore);

cl::opt<bool> m64bits("m64", cl::desc("64 bit target"), cl::ZeroOrMore);

cl::opt<std::string> mTargetTriple("mtriple", cl::ZeroOrMore,
                                   cl::desc("Override target triple"));

cl::opt<std::string>
    mABI("mabi", cl::ZeroOrMore, cl::init(""),
         cl::desc("The name of the ABI to be targeted from the backend"));

static StringsAdapter
    modFileAliasStringsStore("mv", global.params.modFileAliasStrings);
static cl::list<std::string, StringsAdapter> modFileAliasStrings(
    "mv", cl::desc("Use <filespec> as source file for <package.module>"),
    cl::value_desc("<package.module>=<filespec>"),
    cl::location(modFileAliasStringsStore));

cl::list<std::string> includeModulePatterns(
    "i", cl::desc("Include imported modules in the compilation"),
    cl::value_desc("pattern"),
    cl::ValueOptional); // DMD allows omitting a value with special meaning

// Storage for the dynamically created float-abi option.
FloatABI::Type floatABI;

static cl::opt<CHECKENABLE, true, FlagParser<CHECKENABLE>>
    asserts("asserts", cl::ZeroOrMore, cl::desc("(*) Enable assertions"),
            cl::value_desc("bool"), cl::location(global.params.useAssert),
            cl::init(CHECKENABLEdefault));

static cl::opt<CHECKENABLE, true> boundsCheck(
    "boundscheck", cl::ZeroOrMore, cl::desc("Array bounds check"),
    cl::location(global.params.useArrayBounds), cl::init(CHECKENABLEdefault),
    cl::values(clEnumValN(CHECKENABLEoff, "off", "Disabled"),
               clEnumValN(CHECKENABLEsafeonly, "safeonly",
                          "Enabled for @safe functions only"),
               clEnumValN(CHECKENABLEon, "on", "Enabled for all functions")));

static cl::opt<CHECKENABLE, true, FlagParser<CHECKENABLE>> switchErrors(
    "switch-errors", cl::ZeroOrMore,
    cl::desc("(*) Enable runtime errors for unhandled switch cases"),
    cl::location(global.params.useSwitchError), cl::init(CHECKENABLEdefault));

static cl::opt<CHECKENABLE, true, FlagParser<CHECKENABLE>>
    invariants("invariants", cl::ZeroOrMore, cl::desc("(*) Enable invariants"),
               cl::location(global.params.useInvariants),
               cl::init(CHECKENABLEdefault));

static cl::opt<CHECKENABLE, true, FlagParser<CHECKENABLE>>
    preconditions("preconditions", cl::ZeroOrMore,
                  cl::location(global.params.useIn),
                  cl::desc("(*) Enable function preconditions"),
                  cl::init(CHECKENABLEdefault));

static cl::opt<CHECKENABLE, true, FlagParser<CHECKENABLE>>
    postconditions("postconditions", cl::ZeroOrMore,
                   cl::location(global.params.useOut),
                   cl::init(CHECKENABLEdefault),
                   cl::desc("(*) Enable function postconditions"));

static MultiSetter ContractsSetter(false, &global.params.useIn,
                                   &global.params.useOut, nullptr);
static cl::opt<MultiSetter, true, FlagParser<bool>>
    contracts("contracts", cl::ZeroOrMore, cl::location(ContractsSetter),
              cl::desc("(*) Enable function pre- and post-conditions"));

static cl::opt<CHECKACTION, true> checkAction(
    "checkaction", cl::ZeroOrMore, cl::location(global.params.checkAction),
    cl::desc("Action to take when an assert/boundscheck/final-switch fails"),
    cl::init(CHECKACTION_D),
    cl::values(
        clEnumValN(CHECKACTION_D, "D",
                   "Usual D behavior of throwing an AssertError"),
        clEnumValN(CHECKACTION_C, "C",
                   "Call the C runtime library assert failure function"),
        clEnumValN(CHECKACTION_halt, "halt",
                   "Halt the program execution (very lightweight)"),
        clEnumValN(CHECKACTION_context, "context",
                   "Use D assert with context information (when available)")));

static cl::opt<bool, true>
    release("release", cl::ZeroOrMore, cl::location(global.params.release),
            cl::desc("Compile release version, defaulting to disabled "
                     "asserts/contracts/invariants, and bounds checks in @safe "
                     "functions only"),
            cl::ValueDisallowed);

cl::opt<bool, true>
    singleObj("singleobj", cl::desc("Create only a single output object file"),
              cl::ZeroOrMore, cl::location(global.params.oneobj));

cl::opt<uint32_t, true> hashThreshold(
    "hash-threshold", cl::ZeroOrMore, cl::location(global.params.hashThreshold),
    cl::desc("Hash symbol names longer than this threshold (experimental)"));

static cl::opt<LinkonceTemplates, true> linkonceTemplates(
    cl::ZeroOrMore, cl::location(global.params.linkonceTemplates),
    cl::values(
        clEnumValN(LinkonceTemplates::yes, "linkonce-templates",
                   "Use discardable linkonce_odr linkage for template symbols "
                   "and lazily & recursively define all referenced "
                   "instantiated symbols in each object file"),
        clEnumValN(LinkonceTemplates::aggressive,
                   "linkonce-templates-aggressive",
                   "Experimental, more aggressive variant")));

cl::opt<bool> disableLinkerStripDead(
    "disable-linker-strip-dead", cl::ZeroOrMore,
    cl::desc("Do not try to remove unused symbols during linking"),
    cl::cat(linkingCategory));

cl::opt<bool> noPLT(
    "fno-plt", cl::ZeroOrMore,
    cl::desc("Do not use the PLT to make function calls"));

static cl::opt<signed char> passmanager("passmanager",
    cl::desc("Setting the passmanager (new,legacy):"), cl::ZeroOrMore,
    #if LDC_LLVM_VER < 1500
      cl::init(0),
    #else
      cl::init(1),
    #endif
    cl::values(
        clEnumValN(0, "legacy", "Use the legacy passmanager (available for LLVM14 and below) "),
        clEnumValN(1, "new", "Use the new passmanager (available for LLVM14 and above)")));
bool isUsingLegacyPassManager() { return passmanager == 0; }

// Math options
bool fFastMath; // Storage for the dynamically created ffast-math option.
llvm::FastMathFlags defaultFMF;
void setDefaultMathOptions(llvm::TargetOptions &targetOptions) {
  if (fFastMath) {
    defaultFMF.setFast();
    targetOptions.UnsafeFPMath = true;
  }
}

cl::opt<bool>
    fNoDiscardValueNames("fno-discard-value-names", cl::ZeroOrMore,
                         cl::desc("Do not discard value names in LLVM IR"));

cl::opt<bool> fNullPointerIsValid(
    "fno-delete-null-pointer-checks", cl::ZeroOrMore,
    cl::desc(
        "Treat null pointer dereference as defined behavior when optimizing "
        "(instead of _un_defined behavior). This prevents the optimizer from "
        "assuming that any dereferenced pointer must not have been null and "
        "optimize away the branches accordingly."));

cl::opt<bool>
    fNoExceptions("fno-exceptions", cl::ZeroOrMore,
                  cl::desc("Disable generation of exception stack unwinding "
                           "code, assuming no Exceptions will be thrown"));

cl::opt<bool> fNoModuleInfo("fno-moduleinfo", cl::ZeroOrMore,
                            cl::desc("Disable generation of ModuleInfos"));

cl::opt<bool> fNoRTTI("fno-rtti", cl::ZeroOrMore,
                      cl::desc("Disable generation of TypeInfos"));

cl::opt<bool>
    fSplitStack("fsplit-stack", cl::ZeroOrMore,
                cl::desc("Use segmented stack (see Clang documentation)"));

cl::opt<bool, true>
    allinst("allinst", cl::ZeroOrMore, cl::location(global.params.allInst),
            cl::desc("Generate code for all template instantiations"));

cl::opt<unsigned, true> nestedTemplateDepth(
    "template-depth", cl::ZeroOrMore, cl::location(global.recursionLimit),
    cl::init(500),
    cl::desc("Set maximum number of nested template instantiations"));

// legacy options superseded by `-preview=dip<N>`
cl::opt<bool> useDIP25("dip25", cl::ZeroOrMore, cl::ReallyHidden,
                       cl::desc("Implement DIP25 (sealed references)"));
cl::opt<bool> useDIP1000("dip1000", cl::ZeroOrMore, cl::ReallyHidden,
                         cl::desc("Implement DIP1000 (scoped pointers)"));
static cl::opt<bool, true>
    useDIP1008("dip1008", cl::ZeroOrMore, cl::location(global.params.ehnogc),
               cl::desc("Implement DIP1008 (@nogc Throwable)"),
               cl::ReallyHidden);

cl::opt<bool, true> betterC(
    "betterC", cl::ZeroOrMore, cl::location(global.params.betterC),
    cl::desc("Omit generating some runtime information and helper functions"));

// `-cov[=<n>|ctfe]` parser.
struct CoverageParser : public cl::parser<DummyDataType> {
  explicit CoverageParser(cl::Option &O) : cl::parser<DummyDataType>(O) {}

  bool parse(cl::Option &O, llvm::StringRef /*ArgName*/, llvm::StringRef Arg,
             DummyDataType & /*Val*/) {
    global.params.cov = true;

    if (Arg.empty()) {
      return false;
    }

    if (Arg == "ctfe") {
      global.params.ctfe_cov = true;
      return false;
    }

    unsigned char percent = 0;
    if (Arg.getAsInteger(0, percent)) {
      return O.error("'" + Arg +
                     "' value invalid for required coverage percentage");
    }

    if (percent > 100) {
      return O.error("required coverage percentage must be <= 100");
    }

    global.params.covPercent = percent;
    return false;
  }
};

static cl::opt<DummyDataType, false, CoverageParser> coverageAnalysis(
    "cov", cl::ZeroOrMore, cl::ValueOptional,
    cl::desc("Compile-in code coverage analysis and .lst file generation\n"
             "Use -cov=<n> for n% minimum required coverage\n"
             "Use -cov=ctfe to include code executed during CTFE"));

cl::opt<CoverageIncrement> coverageIncrement(
    "cov-increment", cl::ZeroOrMore,
    cl::desc("Set the type of coverage line count increment instruction"),
    cl::init(CoverageIncrement::_default),
    cl::values(clEnumValN(CoverageIncrement::_default, "default",
                          "Use the default (atomic)"),
               clEnumValN(CoverageIncrement::atomic, "atomic", "Atomic increment"),
               clEnumValN(CoverageIncrement::nonatomic, "non-atomic",
                          "Non-atomic increment (not thread safe)"),
               clEnumValN(CoverageIncrement::boolean, "boolean",
                          "Don't read, just set counter to 1")));

// Compilation time tracing options
cl::opt<bool> fTimeTrace(
    "ftime-trace", cl::ZeroOrMore,
    cl::desc("Turn on time profiler. Generates JSON file "
             "based on the output filename (also see --ftime-trace-file)."));
cl::opt<unsigned> fTimeTraceGranularity(
    "ftime-trace-granularity", cl::ZeroOrMore, cl::init(500),
    cl::desc(
        "Minimum time granularity (in microseconds) traced by time profiler"));
cl::opt<std::string>
fTimeTraceFile("ftime-trace-file",
               cl::desc("Specify time trace file destination"),
               cl::value_desc("filename"));

cl::opt<LTOKind> ltoMode(
    "flto", cl::ZeroOrMore, cl::desc("Set LTO mode, requires linker support"),
    cl::init(LTO_None),
    cl::values(
        clEnumValN(LTO_Full, "full", "Merges all input into a single module"),
        clEnumValN(LTO_Thin, "thin",
                   "Parallel importing and codegen (faster than 'full')")));
cl::opt<bool> ltoFatObjects(
    "ffat-lto-objects", cl::ZeroOrMore,
    cl::desc("Include both IR and object code in object file output; only "
             "effective when compiling with -flto."));

cl::opt<std::string>
    saveOptimizationRecord("fsave-optimization-record",
                           cl::value_desc("filename"),
                           cl::desc("Generate a YAML optimization record file "
                                    "of optimizations performed by LLVM"),
                           cl::ValueOptional);

#if LDC_LLVM_VER >= 1300
// LLVM < 13 has "--warn-stack-size", but let's not do the effort of forwarding
// the string to that option, and instead let the user do it himself.
cl::opt<unsigned>
    fWarnStackSize("fwarn-stack-size", cl::ZeroOrMore, cl::init(UINT_MAX),
                   cl::desc("Warn for stack size bigger than the given number"),
                   cl::value_desc("threshold"));
#endif

#if LDC_LLVM_SUPPORTED_TARGET_SPIRV || LDC_LLVM_SUPPORTED_TARGET_NVPTX
cl::list<std::string>
    dcomputeTargets("mdcompute-targets", cl::CommaSeparated,
                    cl::desc("Generates code for the specified DCompute target"
                             " list. Use 'ocl-xy0' for OpenCL x.y, and "
                             "'cuda-xy0' for CUDA CC x.y"),
                     cl::value_desc("targets"));
cl::opt<std::string>
    dcomputeFilePrefix("mdcompute-file-prefix",
                       cl::desc("Prefix to prepend to the generated kernel files."),
                       cl::init("kernels"),
                       cl::value_desc("prefix"));
#endif

#if defined(LDC_DYNAMIC_COMPILE)
cl::opt<bool> enableDynamicCompile(
    "enable-dynamic-compile",
    cl::desc("Enable dynamic compilation"),
    cl::init(false));

cl::opt<bool> dynamicCompileTlsWorkaround(
    "dynamic-compile-tls-workaround",
    cl::desc("Enable dynamic compilation TLS workaround"),
    cl::init(true),
    cl::Hidden);
#endif

#if LDC_LLVM_VER >= 1700
bool enableOpaqueIRPointers = true; // typed pointers are no longer supported from LLVM 17
#elif LDC_LLVM_VER >= 1400
bool enableOpaqueIRPointers = false;
#endif

static cl::extrahelp
    footer("\n"
           "-d-debug can also be specified without options, in which case it "
           "enables all debug checks (i.e. asserts, boundschecks, contracts "
           "and invariants) as well as acting as -d-debug=1.\n\n"
           "Boolean options can take an optional value, e.g., "
           "-link-defaultlib-shared=<true,false>.\n"
           "Boolean options marked with (*) also have a -disable-FOO variant "
           "with inverted meaning.\n");

/// Create commandline options that may clash with LLVM's options (depending on
/// LLVM version and on LLVM configuration), and that thus cannot be created
/// using static construction.
/// The clashing LLVM options are suffixed with "llvm-" and hidden from the
/// -help output.
void createClashingOptions() {
  llvm::StringMap<cl::Option *> &map = cl::getRegisteredOptions();

  auto renameAndHide = [&map](const char *from, const char *to) {
    auto i = map.find(from);
    if (i != map.end()) {
      cl::Option *opt = i->getValue();
      map.erase(i);
      if (to) {
        opt->setArgStr(to);
        opt->setHiddenFlag(cl::Hidden);
        map[to] = opt;
      }
    }
  };

  // Step 1. Hide the clashing LLVM options.
  // LLVM 3.7 introduces compiling as shared library. The result
  // is a clash in the command line options.
  renameAndHide("color", "llvm-color");
  renameAndHide("ffast-math", "llvm-ffast-math");
  renameAndHide("float-abi", "llvm-float-abi");

  // Step 2. Add the LDC options.
  new cl::opt<bool, true, FlagParser<bool>>(
      "color", cl::ZeroOrMore, cl::location(global.params.v.color),
      cl::desc("(*) Force colored console output"));
  new cl::opt<bool, true>("ffast-math", cl::ZeroOrMore, cl::location(fFastMath),
                          cl::desc("Set @fastmath for all functions."));
  new cl::opt<FloatABI::Type, true>(
      "float-abi", cl::desc("ABI/operations to use for floating-point types:"),
      cl::ZeroOrMore, cl::location(floatABI), cl::init(FloatABI::Default),
      cl::values(
          clEnumValN(FloatABI::Default, "default",
                     "Target default floating-point ABI"),
          clEnumValN(FloatABI::Soft, "soft",
                     "Software floating-point ABI and operations"),
          clEnumValN(
              FloatABI::SoftFP, "softfp",
              "Soft-float ABI, but hardware floating-point instructions"),
          clEnumValN(FloatABI::Hard, "hard",
                     "Hardware floating-point ABI and instructions")));

#if LDC_LLVM_VER >= 1400
  renameAndHide("opaque-pointers", nullptr); // remove
  new cl::opt<bool, true>(
      "opaque-pointers", cl::ZeroOrMore, cl::location(enableOpaqueIRPointers),
      cl::desc("Use opaque IR pointers (experimental!)"), cl::Hidden);
#endif
}

/// Hides command line options exposed from within LLVM that are unlikely
/// to be useful for end users from the -help output.
void hideLLVMOptions() {
  static const char *const hiddenOptions[] = {
      "aarch64-neon-syntax", "aarch64-use-aa",
      "abort-on-max-devirt-iterations-reached",
      "addrsig", "align-loops", "allow-ginsert-as-artifact",
      "amdgpu-atomic-optimizer-strategy",
      "amdgpu-bypass-slow-div", "amdgpu-disable-loop-alignment",
      "amdgpu-disable-power-sched", "amdgpu-dpp-combine",
      "amdgpu-dump-hsa-metadata", "amdgpu-enable-flat-scratch",
      "amdgpu-enable-global-sgpr-addr", "amdgpu-enable-merge-m0",
      "amdgpu-enable-power-sched", "amdgpu-igrouplp",
      "amdgpu-promote-alloca-to-vector-limit",
      "amdgpu-reserve-vgpr-for-sgpr-spill", "amdgpu-sdwa-peephole",
      "amdgpu-use-aa-in-codegen", "amdgpu-verify-hsa-metadata",
      "amdgpu-vgpr-index-mode", "arm-add-build-attributes",
      "arm-implicit-it", "asm-instrumentation", "asm-show-inst",
      "atomic-counter-update-promoted", "atomic-first-counter",
      "basic-block-sections",
      "basicblock-sections", "bounds-checking-single-trap", "bpf-stack-size",
      "cfg-hide-cold-paths",
      "cfg-hide-deoptimize-paths", "cfg-hide-unreachable-paths",
      "code-model", "cost-kind", "cppfname", "cppfor", "cppgen",
      "cvp-dont-add-nowrap-flags",
      "cvp-dont-process-adds", "debug-counter", "debug-entry-values",
      "debugger-tune", "debugify-func-limit", "debugify-level",
      "debugify-quiet", "debug-info-correlate",
      "denormal-fp-math", "denormal-fp-math-f32",
      "disable-auto-upgrade-debug-info", "disable-debug-info-verifier",
      "disable-i2p-p2i-opt",
      "disable-objc-arc-checkforcfghazards", "disable-promote-alloca-to-lds",
      "disable-promote-alloca-to-vector", "disable-slp-vectorization",
      "disable-spill-fusing",
      "do-counter-promotion", "dot-cfg-mssa", "dwarf64", "emit-call-site-info",
      "emit-compact-unwind-non-canonical", "emit-dwarf-unwind",
      "emscripten-cxx-exceptions-allowed",
      "emscripten-cxx-exceptions-whitelist",
      "emulated-tls", "enable-approx-func-fp-math", "enable-correct-eh-support",
      "enable-cse-in-irtranslator", "enable-cse-in-legalizer",
      "enable-emscripten-cxx-exceptions", "enable-emscripten-sjlj",
      "enable-fp-mad", "enable-gvn-hoist", "enable-gvn-memdep",
      "enable-gvn-sink", "enable-implicit-null-checks", "enable-jmc-instrument",
      "enable-load-in-loop-pre",
      "enable-load-pre", "enable-loop-simplifycfg-term-folding",
      "enable-misched", "enable-name-compression", "enable-no-infs-fp-math",
      "enable-no-nans-fp-math", "enable-no-signed-zeros-fp-math",
      "enable-no-trapping-fp-math", "enable-objc-arc-annotations",
      "enable-objc-arc-opts", "enable-pie", "enable-scoped-noalias",
      "enable-split-backedge-in-load-pre",
      "enable-tbaa", "enable-unsafe-fp-math", "exception-model",
      "exhaustive-register-search", "expensive-combines",
      "experimental-debug-variable-locations",
      "fatal-assembler-warnings", "filter-print-funcs",
      "force-dwarf-frame-section", "force-opaque-pointers",
      "force-tail-folding-style",
      "fs-profile-debug-bw-threshold", "fs-profile-debug-prob-diff-threshold",
      "generate-merged-base-profiles",
      "gpsize", "hash-based-counter-split", "hexagon-rdf-limit",
      "hot-cold-split", "ignore-xcoff-visibility",
      "imp-null-check-page-size", "imp-null-max-insts-to-consider",
      "import-all-index", "incremental-linker-compatible",
      "instcombine-code-sinking", "instcombine-guard-widening-window",
      "instcombine-max-iterations", "instcombine-max-num-phis",
      "instcombine-max-sink-users",
      "instcombine-maxarray-size", "instcombine-negator-enabled",
      "instcombine-negator-max-depth", "instcombine-unsafe-select-transform",
      "instrprof-atomic-counter-update-all", "internalize-public-api-file",
      "internalize-public-api-list", "iterative-counter-promotion",
      "join-liveintervals", "jump-table-type", "limit-float-precision",
      "lower-global-dtors-via-cxa-atexit",
      "lto-embed-bitcode", "matrix-default-layout",
      "matrix-print-after-transpose-opt", "matrix-propagate-shape",
      "max-counter-promotions", "max-counter-promotions-per-loop",
      "mc-relax-all", "mc-x86-disable-arith-relaxation", "mcabac", "meabi",
      "memop-size-large", "memop-size-range", "merror-missing-parenthesis",
      "merror-noncontigious-register", "mfuture-regs", "mhvx",
      "mips-compact-branches", "mips16-constant-islands", "mips16-hard-float",
      "mir-strip-debugify-only", "misexpect-tolerance", "mlsm", "mno-compound",
      "mno-fixup", "mno-ldc1-sdc1", "mno-pairing", "mwarn-missing-parenthesis",
      "mwarn-noncontigious-register", "mwarn-sign-mismatch", "mxcoff-roptr",
      "no-discriminators", "no-integrated-as", "no-type-check", "no-xray-index",
      "nozero-initialized-in-bss", "nvptx-sched4reg",
      "objc-arc-annotation-target-identifier", "opaque-pointers",
      "pgo-block-coverage", "pgo-temporal-instrumentation",
      "pgo-view-block-coverage-graph",
      "pie-copy-relocations", "poison-checking-function-local",
      "polly-dump-after", "polly-dump-after-file", "polly-dump-before",
      "polly-dump-before-file", "pre-RA-sched", "print-after-all",
      "print-before-all", "print-machineinstrs", "print-module-scope",
      "print-pipeline-passes",
      "profile-estimator-loop-weight", "profile-estimator-loop-weight",
      "profile-file", "profile-info-file", "profile-verifier-noassert",
      "pseudo-probe-for-profiling",
      "r600-ir-structurize", "rdf-dump", "rdf-limit", "recip", "regalloc",
      "relax-elf-relocations", "remarks-section", "rewrite-map-file",
      "riscv-add-build-attributes", "rng-seed",
      "runtime-counter-relocation", "safepoint-ir-verifier-print-only",
      "sample-profile-check-record-coverage",
      "sample-profile-check-sample-coverage",
      "sample-profile-inline-hot-threshold",
      "sample-profile-max-propagate-iterations", "shrink-wrap", "simplify-mir",
      "skip-ret-exit-block",
      "speculative-counter-promotion-max-exiting",
      "speculative-counter-promotion-to-loop", "spiller", "spirv-debug",
      "spirv-erase-cl-md", "spirv-lower-const-expr", "spirv-mem2reg",
      "spirv-no-deref-attr", "spirv-text", "spirv-verify-regularize-passes",
      "split-machine-functions", "spv-dump-deps",
      "spv-lower-saddwithoverflow-validate", "spvbool-validate",
      "spvmemmove-validate", "stack-alignment", "stack-protector-guard",
      "stack-protector-guard-offset", "stack-protector-guard-reg",
      "stack-size-section", "stack-symbol-ordering",
      "stackmap-version", "static-func-full-module-prefix",
      "static-func-strip-dirname-prefix", "stats", "stats-json", "strict-dwarf",
      "strip-debug", "struct-path-tbaa", "summary-file", "sve-tail-folding",
      "swift-async-fp",
      "tail-predication", "tailcallopt", "thinlto-assume-merged",
      "thread-model", "time-passes", "time-trace-granularity", "tls-size",
      "type-based-intrinsic-cost", "unfold-element-atomic-memcpy-max-elements",
      "unique-basic-block-section-names", "unique-bb-section-names",
      "unique-section-names", "unit-at-a-time", "use-ctors",
      "vec-extabi", "verify-debug-info", "verify-dom-info",
      "verify-legalizer-debug-locs", "verify-loop-info",
      "verify-loop-lcssa", "verify-machine-dom-info", "verify-regalloc",
      "verify-region-info", "verify-scev", "verify-scev-maps",
      "vp-counters-per-site", "vp-static-alloc",
      "wasm-enable-eh", "wasm-enable-sjlj",
      "x86-align-branch", "x86-align-branch-boundary",
      "x86-branches-within-32B-boundaries", "x86-early-ifcvt",
      "x86-pad-max-prefix-size",
      "x86-recip-refinement-steps", "x86-use-vzeroupper",
      "xcoff-traceback-table", "xray-function-index",

      // We enable -fdata-sections/-ffunction-sections by default where it makes
      // sense for reducing code size, so hide them to avoid confusion.
      //
      // We need our own switch as these two are defined by LLVM and linked to
      // static TargetMachine members, but the default we want to use depends
      // on the target triple (and thus we do not know it until after the
      // command line has been parsed).
      "fdata-sections", "ffunction-sections", "data-sections",
      "function-sections"};

  // pulled in from shared LLVM headers, but unused or not desired in LDC
  static const char *const removedOptions[] = {"disable-tail-calls",
                                               "fatal-warnings",
                                               "filetype",
                                               "no-deprecated-warn",
                                               "no-warn",
                                               "stackrealign",
                                               "start-after",
                                               "stop-after",
                                               "trap-func",
                                               "W"};

  llvm::StringMap<cl::Option *> &map = cl::getRegisteredOptions();
  for (const auto name : hiddenOptions) {
    // Check if option exists first for resilience against LLVM changes
    // between versions.
    auto it = map.find(name);
    if (it != map.end()) {
      it->second->setHiddenFlag(cl::Hidden);
    }
  }

  for (const auto name : removedOptions) {
    auto it = map.find(name);
    if (it != map.end()) {
      map.erase(it);
    }
  }
}

} // namespace opts
